/**
 * Copyright 2019-2022 Huawei Technologies Co., Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "base/common/file_util/file_util.h"

#include <climits>
#include <functional>
#include <fcntl.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <ctime>
#include <cstdlib>
#include <regex>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>

#include "infra/base/assertion.h"
#include "infra/base/securestl.h"
#include "framework/infra/log/log.h"

using namespace std;

namespace hiai {
static int64_t MAX_SUPPORTED_FILE_SIZE = 0x1C0000000ULL; // 7GB
static const int MAX_FILE_SIZE_LIMIT =
    INT_MAX;  // 输入文件的最大长度。基于安全编码规范和目前实际(protobuf)模型大小，定为2G。

FILE* FileUtil::OpenFile(const std::string& fileName, const std::string& mode)
{
    if (fileName.empty()) {
        FMK_LOGE("fileName is null.");
        return nullptr;
    }

    HIAI_EXPECT_TRUE_R(fileName.size() < PATH_MAX, nullptr);
    char pathTemp[PATH_MAX + 1] = {0x00};
    if (realpath(fileName.c_str(), pathTemp) == nullptr) {
        FMK_LOGE("fileName: %s is invalid.", fileName.c_str());
        return nullptr;
    }

    return fopen(pathTemp, mode.c_str());
}

void FileUtil::CloseFile(FILE* fp)
{
    if (fp == nullptr) {
        return;
    }
    fclose(fp);
}

long FileUtil::GetFileSize(FILE* fp)
{
    if (fp == nullptr) {
        return -1;
    }

    if (fseek(fp, 0, SEEK_END) != 0) {
        FMK_LOGE("fseek SEEK_END error.");
        return -1;
    }

    long fileSize = ftell(fp);
    if (fileSize <= 0) {
        FMK_LOGE("ftell failed.");
    }

    if (fseek(fp, 0, SEEK_SET) != 0) {
        FMK_LOGW("fseek SEEK_SET error.");
    }
    return fileSize;
}

long FileUtil::GetFileSize(const std::string& fileName)
{
    FILE* fp = OpenFile(fileName, "r");
    long fileSize = GetFileSize(fp);
    CloseFile(fp);

    return fileSize;
}

static std::shared_ptr<BaseBuffer> LoadToBufferBySize(FILE* fp, size_t size)
{
    std::shared_ptr<BaseBuffer> buffer = make_shared_nothrow<BaseBuffer>();
    if (buffer == nullptr) {
        FMK_LOGE("make shared failed.");
        return nullptr;
    }

    uint8_t* data = new (std::nothrow) uint8_t[size];
    HIAI_EXPECT_TRUE_R(data != nullptr, nullptr);
    buffer->SetData(data, size, true);

    size_t readSize = fread(buffer->MutableData(), sizeof(uint8_t), size, fp);
    if (readSize != size) {
        FMK_LOGE("read failed.");
        return nullptr;
    }

    return buffer;
}

std::shared_ptr<BaseBuffer> FileUtil::LoadToBuffer(const std::string& fileName)
{
    std::unique_ptr<FILE, std::function<void(FILE*)>> fp(OpenFile(fileName, "r"), [](FILE* p) { fclose(p); });

    if (fp == nullptr) {
        FMK_LOGE("open file failed.");
        return nullptr;
    }
    long fileSize = GetFileSize(fp.get());
    if (fileSize <= 0 || fileSize > MAX_SUPPORTED_FILE_SIZE) {
        FMK_LOGE("unsupported file size[%ld].", fileSize);
        return nullptr;
    }

    return LoadToBufferBySize(fp.get(), static_cast<size_t>(fileSize));
}

std::shared_ptr<BaseBuffer> FileUtil::LoadToBuffer(const std::string& fileName, size_t size)
{
    std::unique_ptr<FILE, std::function<void(FILE*)>> fp(OpenFile(fileName, "r"), [](FILE* p) { fclose(p); });
    if (fp == nullptr) {
        FMK_LOGE("open file failed.");
        return nullptr;
    }
    long fileSize = GetFileSize(fp.get());
    if (fileSize <= 0 || static_cast<size_t>(fileSize) < size) {
        FMK_LOGE("insufficient size[%zu].", size);
        return nullptr;
    }

    return LoadToBufferBySize(fp.get(), size);
}

static Status CheckFilePath(std::string& resolvedPathStr)
{
    if (resolvedPathStr.empty()) {
        FMK_LOGE("resolvedPath is empty!");
        return FAILURE;
    }

    std::string dirName = "";
    std::string outputFileName = "";

    size_t lastSplitIndex = resolvedPathStr.find_last_of("/\\");
    if (lastSplitIndex == std::string::npos) {
        dirName = "./";
        outputFileName = resolvedPathStr;
    } else {
        dirName = resolvedPathStr.substr(0, lastSplitIndex + 1);
        outputFileName = resolvedPathStr.substr(lastSplitIndex + 1);
    }

    if (outputFileName.empty()) {
        FMK_LOGE("outputFileName is empty!");
        return FAILURE;
    }

    char resolvedOutputPath[PATH_MAX + 1] = {0x00};
    if (realpath(dirName.c_str(), resolvedOutputPath) == nullptr) {
        FMK_LOGE("invalid output file path: %s", dirName.c_str());
        return FAILURE;
    }
    resolvedPathStr = std::string(resolvedOutputPath) + "/" + outputFileName;
    return SUCCESS;
}

Status FileUtil::CreateEmptyFile(const char* file)
{
    HIAI_EXPECT_NOT_NULL(file);
    std::string resolvedPathStr = file;
    HIAI_EXPECT_EXEC(CheckFilePath(resolvedPathStr));
    FILE* fp = fopen(resolvedPathStr.c_str(), "wb");
    if (fp == nullptr) {
        FMK_LOGE("open model fail, because not found file path");
        return FAILURE;
    }

    fclose(fp);
    return SUCCESS;
}

Status FileUtil::WriteBufferToFile(const void* data, uint32_t size, const char* file)
{
    HIAI_EXPECT_NOT_NULL(file);
    HIAI_EXPECT_NOT_NULL(data);
    std::string resolvedPathStr = file;
    HIAI_EXPECT_EXEC(CheckFilePath(resolvedPathStr));
    FILE* fp = fopen(resolvedPathStr.c_str(), "ab");
    if (fp == nullptr) {
        FMK_LOGE("open model fail, because not found file path");
        return FAILURE;
    }

    uint32_t writeSize = static_cast<uint32_t>(fwrite(data, 1, size, fp));
    if (writeSize != size) {
        fclose(fp);
        FMK_LOGE("WriteBufferToFile ERROR: writeSize(%u) != size(%u)", writeSize, size);
        return FAILURE;
    }

    fclose(fp);
    return SUCCESS;
}

// 获取文件长度
HCS_API_EXPORT int GetFileLength(const std::string& inputFile)
{
    HIAI_EXPECT_TRUE_R(!(inputFile.empty()), -1);
    string realPath = RealPath(inputFile.c_str());

    HIAI_EXPECT_TRUE_R(!(realPath.empty()), -1);

    struct stat buf;
    if (stat(realPath.c_str(), &buf) != 0) {
        FMK_LOGE("stat file failed.");
        return -1;
    }
    if ((buf.st_mode & S_IFDIR) == S_IFDIR) {
        FMK_LOGE("%s is not file", inputFile.c_str());
        return -1;
    }

    HIAI_EXPECT_TRUE_R((buf.st_size > 0), -1);

    HIAI_EXPECT_TRUE_R((buf.st_size <= MAX_FILE_SIZE_LIMIT), -1);

    return static_cast<int>(buf.st_size);
}

/**
 *  @ingroup domi_common
 *  @brief 创建目录，支持创建多级目录
 *  @param [in] directoryPath  路径，可以为多级目录
 *  @return -1 失败
 *  @return 0 成功
 */
HCS_API_EXPORT int CreateDir(const std::string& directoryPath)
{
    HIAI_EXPECT_TRUE_R(!(directoryPath.empty()), -1);
    uint32_t dirPathLen = directoryPath.length();
    if (dirPathLen >= DOMI_MAX_PATH_LEN) {
        FMK_LOGE("directory path is too long.");
        return -1;
    }
    char tmpDirPath[DOMI_MAX_PATH_LEN] = {0};
    for (uint32_t i = 0; i < dirPathLen; i++) {
        tmpDirPath[i] = directoryPath[i];
        if ((tmpDirPath[i] == '\\') || (tmpDirPath[i] == '/')) {
            if (access(tmpDirPath, F_OK) == 0) {
                continue;
            }
            int32_t ret = mkdir(tmpDirPath, S_IRUSR | S_IWUSR | S_IXUSR);
            if (ret != 0 && errno != EEXIST) {
                FMK_LOGE("Cannot create directory %s. Make sure that the directory exists and writable.",
                    directoryPath.c_str());
                return ret;
            }
        }
    }
    if ((access(directoryPath.c_str(), F_OK) != 0)) {
        int32_t ret = mkdir(directoryPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); // 700
        if (ret != 0 && errno != EEXIST) {
            FMK_LOGE("Cannot create directory %s. Make sure that the directory exists and writable.",
                directoryPath.c_str());
            return ret;
        }
    }
    return 0;
}

HCS_API_EXPORT string RealPath(const char* path)
{
    HIAI_EXPECT_NOT_NULL_R(path, "");
    HIAI_EXPECT_TRUE_R((strlen(path) < PATH_MAX), "");
    // PATH_MAX使系统自带的宏，表示支持的最大文件路径长度
    char* resolvedPath = new (std::nothrow) char[PATH_MAX] {0};
    HIAI_EXPECT_NOT_NULL_R(resolvedPath, "");

    string res = "";

    // 路径不存在，或者没有权限时，会返回nullptr
    // 路径可访问时，返回绝对路径
    if (realpath(path, resolvedPath) != nullptr) {
        res = resolvedPath;
    }

    if (resolvedPath != nullptr) {
        delete[] resolvedPath;
    }
    return res;
}

HCS_API_EXPORT bool CheckInputPathValid(const string& filePath)
{
    // 指定路径为空
    if (filePath.empty()) {
        FMK_LOGE("path is empty.");
        return false;
    }

    // 校验输入文件路径是否合法的正则匹配表达式
    // ^(/|./|(../)+|)([.]?[A-Za-z0-9_-]+/)*[A-Za-z0-9_+.-]+$
    // 路径部分：支持大小写字母、数字，下划线
    // 文件名部分：支持大小写字母、数字，下划线和点(.)
    string mode = "^(/+|./+|(../+)+|)(../|([.]?[A-Za-z0-9_+.-]+)/+)*[A-Za-z0-9_+.-]+$";
    HIAI_EXPECT_TRUE_R((ValidateStr(filePath, mode)), false);

    string realPath = RealPath(filePath.c_str());
    // 无法获取绝对路径(不存在，或者无权限访问)
    if (realPath.empty()) {
        FMK_LOGE("can not get real path for %s.", filePath.c_str());
        return false;
    }

    // 绝对路径指向的文件不可读
    if (access(realPath.c_str(), R_OK) != 0) {
        FMK_LOGE("can not read file in %s.", filePath.c_str());
        return false;
    }

    return true;
}

HCS_API_EXPORT bool CheckOutputPathValid(const string& filePath)
{
    // 指定路径为空
    if (filePath.empty()) {
        FMK_LOGE("path is empty.");
        return false;
    }

    // 校验输入文件路径是否合法的正则匹配表达式
    // ^(/|./|(../)+|)([.]?[A-Za-z0-9_-]+/)*[A-Za-z0-9_+.-]+$
    // 路径部分：支持大小写字母、数字，下划线
    // 文件名部分：支持大小写字母、数字，下划线和点(.)
    string mode = "^(/+|./+|(../+)+|)(../|([.]?[A-Za-z0-9_-]+)/+)*[A-Za-z0-9_+.-]+$";
    HIAI_EXPECT_TRUE_R((ValidateStr(filePath, mode)), false);
    string realPath = RealPath(filePath.c_str());
    // 可以获取绝对路径(文件存在)
    if (!realPath.empty()) {
        // 文件不可读写
        if (access(realPath.c_str(), R_OK | W_OK | F_OK) != 0) {
            FMK_LOGE("path[ %s ] exists, but can not be write.", filePath.c_str());
            return false;
        }
        return true;
    }
    // 无法获取绝对路径。 1. 无权限 2. 文件路径不存在。通过创建路径的方式，判断目录是否有权访问.
    // 寻找最后一个分割符
    int pathSplitPos = static_cast<int>(filePath.size()) - 1;
    for (; pathSplitPos >= 0; pathSplitPos--) {
        if (filePath[pathSplitPos] == '\\' || filePath[pathSplitPos] == '/') {
            break;
        }
    }
    if (pathSplitPos == 0) {
        return true;
    }
    if (pathSplitPos != -1) { // 可以找到路径分隔符
        string prefixPath = std::string(filePath).substr(0, pathSplitPos);
        // 通过创建路径的方式，判断指定路径是否有效
        if (CreateDir(prefixPath) != 0) { // 没有权限创建路径
            FMK_LOGE("can not create prefix path for path[ %s ].", filePath.c_str());
            return false;
        }
    }

    return true;
}

HCS_API_EXPORT bool ValidateStr(const std::string& filePath, const std::string& mode)
{
    regex reg(mode);

    // 匹配上的字符串部分
    smatch match;
    bool res = regex_match(filePath, match, reg);
    HIAI_EXPECT_TRUE_R(res, res);
    res = !regex_search(filePath, regex("[`!@#$%^&*()|{}':;',\\[\\]<>?]"));

    return res && (filePath.size() == match.str().size());
}
HCS_API_EXPORT int ReadFile(const char* filePath, uint8_t*& addr, size_t& size)
{
    std::string realPath = RealPath(filePath);
    if (realPath.empty()) {
        return -1;
    }
    std::ifstream fs(realPath.c_str(), std::ifstream::binary);
    if (!fs.is_open()) {
        return -1;
    }
    // get length of file:
    fs.seekg(0, fs.end);
    int64_t len = fs.tellg();
    if (len <= 0) {
        fs.close();
        return -1;
    }
    fs.seekg(0, fs.beg);
    auto* data = new (std::nothrow) uint8_t[len];
    if (data == nullptr) {
        fs.close();
        return -1;
    }
    // read data as a block:
    fs.read(reinterpret_cast<char*>(data), len);
    fs.close();
    // 设置model_data参数
    addr = data;
    size = static_cast<size_t>(len);

    return 0;
}

HCS_API_EXPORT int ReadFileOnly(const char* filePath, uint8_t*& addr, size_t& size)
{
    std::string realPath = RealPath(filePath);
    if (realPath.empty()) {
        return -1;
    }

    int fd = open(realPath.c_str(), O_RDONLY);
    if (fd == -1) {
        FMK_LOGE("open file[%s] fail", filePath);
        return -1;
    }
    int64_t len = lseek64(fd, 0, SEEK_END);
    if (len <= 0) {
        close(fd);
        return -1;
    }

    void* data = mmap(nullptr, len, PROT_READ, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        FMK_LOGE("mmap fail");
        close(fd);
        return -1;
    }
    close(fd);

    addr = (uint8_t*)data;
    size = static_cast<size_t>(len);

    return 0;
}

HCS_API_EXPORT void ReleaseFileMemory(uint8_t* addr, size_t size)
{
    (void)munmap(addr, size);
}
} // namespace hiai
